package luajit

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"

	"github.com/go-playground/validator/v10"
	"gitverse.ru/dlc/similarity-compiler/pkg/errors"
)

type Deps struct {
	LuaJITBinPath string `validate:"required"`
}

type Compiler struct {
	luaJITBinPath string
}

func New(deps Deps) (*Compiler, error) {
	if err := validator.New().Struct(deps); err != nil {
		return nil, errors.NewWithError(fmt.Errorf("validate deps: %w", err), errors.ErrInternalError)
	}

	stat, err := os.Stat(deps.LuaJITBinPath)
	if err != nil {
		return nil, errors.NewWithError(fmt.Errorf("get lua bin stat: %w", err), errors.ErrInternalError)
	}

	if stat.IsDir() {
		return nil, errors.NewWithMessage("lua jit bin is directory, expected not directory", errors.ErrInternalError)
	}

	return &Compiler{
		luaJITBinPath: deps.LuaJITBinPath,
	}, nil
}

func (c *Compiler) Compile(ctx context.Context, code []byte) ([]byte, error) {
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	cmd := exec.CommandContext(ctx, c.luaJITBinPath, "-b", "-", "-") //nolint:gosec

	cmd.Stderr = stderr
	cmd.Stdout = stdout

	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Fatal(err)
	}

	if _, err := stdin.Write(code); err != nil {
		return nil, fmt.Errorf("write script: %w", err)
	}

	if err := stdin.Close(); err != nil {
		return nil, fmt.Errorf("close stdin: %w", err)
	}

	if err := cmd.Run(); err != nil {
		if errString := stderr.String(); errString != "" {
			return nil, fmt.Errorf("run %w: stderr: %s", err, errString)
		}

		return nil, fmt.Errorf("run: %w", err)
	}

	out, err := io.ReadAll(stdout)
	if err != nil {
		return nil, fmt.Errorf("read stdout: %w", err)
	}

	return out, nil
}
